/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.xml;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static axil.framework.Functions.*;


/**
 * A factory for creating objects based upon use of the @XML annotation.
 */
class AnnotationFactory extends XMLFactory {
	private String tag;
	private Set<String> tags;
	private Map<String,XMLFactory> types;


	AnnotationFactory(Class type, XML x) {
		tag = x.name();
		types = new HashMap<String, XMLFactory>();
		tags = new HashSet<String>();
		Class[] list = x.types();
		if ((list == null) || (list.length == 0)) {
			throw abort(
			    "The abstract type defines an XML annotation, but it does not " +
			    "specify any concrete types.", nv("type", type));
		}
		for (Class t : list) {
			XML tx = (XML)t.getAnnotation(XML.class);
			if ((tx == null) || empty(tx.name()) || tx.name().equals("###")) {
				throw abort(
				    "The type was explicitly referenced as a concrete " +
				    "implementation of the abstract type, but the type does " +
				    "not declare an @XML annotation for its XML tag name.",
				    nv("abstract-type", type),
				    nv("concrete-type", t));
			}
			if (tags.contains(tx.name())) {
				throw abort(
				    "More than one class declares itself to have that tag " +
				    "name. All tag names must be unique within their scope.",
				    nv("name", tx.name()), nv("duplicate", t));
			}
			tags.add(tx.name());
			types.put(tx.name(), XMLFactory.discover(t));
		}
	}


	/**
	 * Get the name of the tag used to represent the object constructed by this
	 * factory. If no such tag is known, then an exception is thrown; the value
	 * returned is never null.
	 */
	public String tag() {
		return tag;
	}


	/**
	 * Tell if this factory recognizes the given tag and can construct an object
	 * from it. This is only used when dealing with abstract types.
	 */
	public boolean matches(String tag) {
		return tags.contains(tag);
	}


	/**
	 * Construct a new instance of the type represented by this factory object.
	 * The value returned is never null.
	 */
	public <T> T construct(InputXML xml) {
		return types.get(xml.identity()).construct(xml);
	}
}
